'\" t
.TH "ORG\&.FREEDESKTOP\&.LOGCONTROL1" "5" "" "systemd 257.1" "org.freedesktop.LogControl1"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.\" http://bugs.debian.org/507673
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.ie \n(.g .ds Aq \(aq
.el       .ds Aq '
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
.\" disable hyphenation
.nh
.\" disable justification (adjust text to left margin only)
.ad l
.\" -----------------------------------------------------------------
.\" * MAIN CONTENT STARTS HERE *
.\" -----------------------------------------------------------------
.SH "NAME"
org.freedesktop.LogControl1 \- D\-Bus interface to query and set logging configuration
.SH "INTRODUCTION"
.PP
org\&.freedesktop\&.LogControl1
is a generic interface that is intended to be used by any daemon which allows the log level and target to be set over D\-Bus\&. It is implemented by various daemons that are part of the
\fBsystemd\fR(1)
suite\&.
.PP
It is assumed that those settings are global for the whole program, so a fixed object path is used\&. The interface should always be available under the path
/org/freedesktop/LogControl1\&.
.SH "DESCRIPTION"
.PP
The following interface is exposed:
.sp
.if n \{\
.RS 4
.\}
.nf
node /org/freedesktop/LogControl1 {
  interface org\&.freedesktop\&.LogControl1 {
    properties:
      @org\&.freedesktop\&.DBus\&.Property\&.EmitsChangedSignal("false")
      @org\&.freedesktop\&.systemd1\&.Privileged("true")
      readwrite s LogLevel = \*(Aq\&.\&.\&.\*(Aq;
      @org\&.freedesktop\&.DBus\&.Property\&.EmitsChangedSignal("false")
      @org\&.freedesktop\&.systemd1\&.Privileged("true")
      readwrite s LogTarget = \*(Aq\&.\&.\&.\*(Aq;
      @org\&.freedesktop\&.DBus\&.Property\&.EmitsChangedSignal("false")
      readonly s SyslogIdentifier = \*(Aq\&.\&.\&.\*(Aq;
  };
  interface org\&.freedesktop\&.DBus\&.Peer { \&.\&.\&. };
  interface org\&.freedesktop\&.DBus\&.Introspectable { \&.\&.\&. };
  interface org\&.freedesktop\&.DBus\&.Properties { \&.\&.\&. };
};
.fi
.if n \{\
.RE
.\}




.SS "Properties"
.PP
\fILogLevel\fR
describes the
\fBsyslog\fR(3)\-style log\-level, and should be one of
"emerg",
"alert",
"crit",
"err",
"warning",
"notice",
"info",
"debug", in order of increasing verbosity\&.
.PP
\fILogTarget\fR
describes the log target (mechanism)\&. It should be one of
"console"
(log to the console or standard output),
"kmsg"
(log to the kernel ring buffer),
"journal"
(log to the journal natively, see
\fBsystemd-journald.service\fR(8)),
"syslog"
(log using the
\fBsyslog\fR(3)
call)\&.
.if n \{\
.sp
.\}
.RS 4
.it 1 an-trap
.nr an-no-space-flag 1
.nr an-break-flag 1
.br
.ps +1
\fBWrite Access\fR
.ps -1
.br
.PP
The
\fILogLevel\fR
and
\fILogTarget\fR
properties are supposed to be writable\&. Care should be taken to ensure that only appropriately privileged clients can modify them\&.
.sp .5v
.RE
.PP
\fISyslogIdentifier\fR
is a read\-only property that shows the "syslog identifier"\&. It is a short string that identifies the program that is the source of log messages that is passed to the
\fBsyslog\fR(3)
call\&.
.SH "TOOLS"
.PP
\fBjournalctl\fR
option
\fB\-p\fR/\fB\-\-priority=\fR
may be used to filter log messages by log level, option
\fB\-t\fR/\fB\-\-identifier=\fR
may be used to by the syslog identifier, and filters like
"_TRANSPORT=syslog",
"_TRANSPORT=journal", and
"_TRANSPORT=kernel"
may be used to filter messages by the mechanism through which they reached
\fBsystemd\-journald\fR\&.
.PP
\fBsystemctl log\-level\fR
and
\fBsystemctl log\-target\fR
verbs may be used to query and set the
\fILogLevel\fR
and
\fILogTarget\fR
properties of the service manager\&.
\fBsystemctl service\-log\-level\fR
and
\fBsystemctl service\-log\-target\fR
may similarly be used for individual services\&. (Services must have the
\fIBusName=\fR
property set and must implement the interface described here\&. See
\fBsystemd.service\fR(5)
for details about
\fIBusName=\fR\&.)
.SH "EXAMPLE"
.PP
\fBExample\ \&1.\ \&Create a simple listener on the bus that implements LogControl1\fR
.sp
.if n \{\
.RS 4
.\}
.nf
/* SPDX\-License\-Identifier: MIT\-0 */

/* Implements the LogControl1 interface as per specification:
 * https://www\&.freedesktop\&.org/software/systemd/man/org\&.freedesktop\&.LogControl1\&.html
 *
 * Compile with \*(Aqcc logcontrol\-example\&.c $(pkg\-config \-\-libs \-\-cflags libsystemd)\*(Aq
 *
 * To get and set properties via busctl:
 *
 * $ busctl \-\-user get\-property org\&.freedesktop\&.Example \e
 *                              /org/freedesktop/LogControl1 \e
 *                              org\&.freedesktop\&.LogControl1 \e
 *                              SyslogIdentifier
 *   s "example"
 * $ busctl \-\-user get\-property org\&.freedesktop\&.Example \e
 *                              /org/freedesktop/LogControl1 \e
 *                              org\&.freedesktop\&.LogControl1 \e
 *                              LogTarget
 *   s "journal"
 * $ busctl \-\-user get\-property org\&.freedesktop\&.Example \e
 *                              /org/freedesktop/LogControl1 \e
 *                              org\&.freedesktop\&.LogControl1 \e
 *                              LogLevel
 *   s "info"
 * $ busctl \-\-user set\-property org\&.freedesktop\&.Example \e
 *                              /org/freedesktop/LogControl1 \e
 *                              org\&.freedesktop\&.LogControl1 \e
 *                              LogLevel \e
 *                              "s" debug
 * $ busctl \-\-user get\-property org\&.freedesktop\&.Example \e
 *                              /org/freedesktop/LogControl1 \e
 *                              org\&.freedesktop\&.LogControl1 \e
 *                              LogLevel
 *   s "debug"
 */

#include <errno\&.h>
#include <stdlib\&.h>
#include <stdio\&.h>
#include <syslog\&.h>
#include <systemd/sd\-bus\&.h>
#include <systemd/sd\-journal\&.h>

#define _cleanup_(f) __attribute__((cleanup(f)))

static int log_error(int log_level, int error, const char *str) {
  sd_journal_print(log_level, "%s failed: %s", str, strerror(\-error));
  return error;
}

typedef enum LogTarget {
  LOG_TARGET_JOURNAL,
  LOG_TARGET_KMSG,
  LOG_TARGET_SYSLOG,
  LOG_TARGET_CONSOLE,
  _LOG_TARGET_MAX,
} LogTarget;

static const char* const log_target_table[_LOG_TARGET_MAX] = {
  [LOG_TARGET_JOURNAL] = "journal",
  [LOG_TARGET_KMSG]    = "kmsg",
  [LOG_TARGET_SYSLOG]  = "syslog",
  [LOG_TARGET_CONSOLE] = "console",
};

static const char* const log_level_table[LOG_DEBUG + 1] = {
  [LOG_EMERG]   = "emerg",
  [LOG_ALERT]   = "alert",
  [LOG_CRIT]    = "crit",
  [LOG_ERR]     = "err",
  [LOG_WARNING] = "warning",
  [LOG_NOTICE]  = "notice",
  [LOG_INFO]    = "info",
  [LOG_DEBUG]   = "debug",
};

typedef struct object {
  const char *syslog_identifier;
  LogTarget log_target;
  int log_level;
} object;

static int property_get(
                sd_bus *bus,
                const char *path,
                const char *interface,
                const char *property,
                sd_bus_message *reply,
                void *userdata,
                sd_bus_error *error) {

  object *o = userdata;

  if (strcmp(property, "LogLevel") == 0)
    return sd_bus_message_append(reply, "s", log_level_table[o\->log_level]);

  if (strcmp(property, "LogTarget") == 0)
    return sd_bus_message_append(reply, "s", log_target_table[o\->log_target]);

  if (strcmp(property, "SyslogIdentifier") == 0)
    return sd_bus_message_append(reply, "s", o\->syslog_identifier);

  return sd_bus_error_setf(error,
                           SD_BUS_ERROR_UNKNOWN_PROPERTY,
                           "Unknown property \*(Aq%s\*(Aq",
                           property);
}

static int property_set(
                sd_bus *bus,
                const char *path,
                const char *interface,
                const char *property,
                sd_bus_message *message,
                void *userdata,
                sd_bus_error *error) {

  object *o = userdata;
  const char *value;
  int r;

  r = sd_bus_message_read(message, "s", &value);
  if (r < 0)
    return r;

  if (strcmp(property, "LogLevel") == 0) {
    int i;
    for (i = 0; i < LOG_DEBUG + 1; i++)
      if (strcmp(value, log_level_table[i]) == 0) {
        o\->log_level = i;
        setlogmask(LOG_UPTO(i));
        return 0;
      }

    return sd_bus_error_setf(error,
                             SD_BUS_ERROR_INVALID_ARGS,
                             "Invalid value for LogLevel: \*(Aq%s\*(Aq",
                             value);
  }

  if (strcmp(property, "LogTarget") == 0) {
    LogTarget i;
    for (i = 0; i < _LOG_TARGET_MAX; i++)
      if (strcmp(value, log_target_table[i]) == 0) {
        o\->log_target = i;
        return 0;
      }

    return sd_bus_error_setf(error,
                             SD_BUS_ERROR_INVALID_ARGS,
                             "Invalid value for LogTarget: \*(Aq%s\*(Aq",
                             value);
  }

  return sd_bus_error_setf(error,
                           SD_BUS_ERROR_UNKNOWN_PROPERTY,
                           "Unknown property \*(Aq%s\*(Aq",
                           property);
}

/* https://www\&.freedesktop\&.org/software/systemd/man/sd_bus_add_object\&.html
 */
static const sd_bus_vtable vtable[] = {
  SD_BUS_VTABLE_START(0),
  SD_BUS_WRITABLE_PROPERTY(
    "LogLevel", "s",
    property_get, property_set,
    0,
    0),
  SD_BUS_WRITABLE_PROPERTY(
    "LogTarget", "s",
    property_get, property_set,
    0,
    0),
  SD_BUS_PROPERTY(
    "SyslogIdentifier", "s",
    property_get,
    0,
    SD_BUS_VTABLE_PROPERTY_CONST),
  SD_BUS_VTABLE_END
};

int main(int argc, char **argv) {
  /* The bus should be relinquished before the program terminates\&. The cleanup
   * attribute allows us to do it nicely and cleanly whenever we exit the
   * block\&.
   */
  _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;

  object o = {
    \&.log_level = LOG_INFO,
    \&.log_target = LOG_TARGET_JOURNAL,
    \&.syslog_identifier = "example",
  };
  int r;

  /* https://man7\&.org/linux/man\-pages/man3/setlogmask\&.3\&.html
   * Programs using syslog() instead of sd_journal can use this API to cut logs
   * emission at the source\&.
   */
  setlogmask(LOG_UPTO(o\&.log_level));

  /* Acquire a connection to the bus, letting the library work out the details\&.
   * https://www\&.freedesktop\&.org/software/systemd/man/sd_bus_default\&.html
   */
  r = sd_bus_default(&bus);
  if (r < 0)
    return log_error(o\&.log_level, r, "sd_bus_default()");

  /* Publish an interface on the bus, specifying our well\-known object access
   * path and public interface name\&.
   * https://www\&.freedesktop\&.org/software/systemd/man/sd_bus_add_object\&.html
   * https://dbus\&.freedesktop\&.org/doc/dbus\-tutorial\&.html
   */
  r = sd_bus_add_object_vtable(bus, NULL,
                               "/org/freedesktop/LogControl1",
                               "org\&.freedesktop\&.LogControl1",
                               vtable,
                               &o);
  if (r < 0)
    return log_error(o\&.log_level, r, "sd_bus_add_object_vtable()");

  /* By default the service is assigned an ephemeral name\&. Also add a fixed
   * one, so that clients know whom to call\&.
   * https://www\&.freedesktop\&.org/software/systemd/man/sd_bus_request_name\&.html
   */
  r = sd_bus_request_name(bus, "org\&.freedesktop\&.Example", 0);
  if (r < 0)
    return log_error(o\&.log_level, r, "sd_bus_request_name()");

  for (;;) {
    /* https://www\&.freedesktop\&.org/software/systemd/man/sd_bus_wait\&.html
     */
    r = sd_bus_wait(bus, UINT64_MAX);
    if (r < 0)
      return log_error(o\&.log_level, r, "sd_bus_wait()");
    /* https://www\&.freedesktop\&.org/software/systemd/man/sd_bus_process\&.html
     */
    r = sd_bus_process(bus, NULL);
    if (r < 0)
      return log_error(o\&.log_level, r, "sd_bus_process()");
  }

  /* https://www\&.freedesktop\&.org/software/systemd/man/sd_bus_release_name\&.html
   */
  r = sd_bus_release_name(bus, "org\&.freedesktop\&.Example");
  if (r < 0)
    return log_error(o\&.log_level, r, "sd_bus_release_name()");

  return 0;
}
.fi
.if n \{\
.RE
.\}
.PP
This creates a simple server on the bus\&. It implements the LogControl1 interface by providing the required properties and allowing to set the writable ones\&. It logs at the configured log level using
\fBsd_journal_print\fR(3)\&.
.PP
Note that when porting this example to other D\-Bus libraries it might be necessary to add manual client privilege checks, as they typically do not default to the restrictive defaults of sd\-bus, where unprivileged access to properties is controlled via the
\fBSD_BUS_VTABLE_UNPRIVILEGED\fR
flag that is opt\-in rather than opt\-out\&.
.SH "SEE ALSO"
.PP
\fBsystemd\fR(1), \fBjournalctl\fR(1), \fBsystemctl\fR(1), \fBsystemd.service\fR(5), \fBsyslog\fR(3)
